package net.airtheva.hexagonMatrix.data;

import java.lang.Math;

// This class is copied from
// http://profs.etsmtl.ca/mmcguffin/code/java/SimpleModeller-3D_JavaApplication-JOGL-OpenGL/Matrix4x4.java
// and modified by unknown.

public class Matrix4x4f {

	public float [] m = new float[16];

	public Matrix4x4f() {
		LoadIdentity();
	}

	public void LoadIdentity() {
		m[ 0] = 1; m[ 4] = 0; m[ 8] = 0; m[12] = 0;
		m[ 1] = 0; m[ 5] = 1; m[ 9] = 0; m[13] = 0;
		m[ 2] = 0; m[ 6] = 0; m[10] = 1; m[14] = 0;
		m[ 3] = 0; m[ 7] = 0; m[11] = 0; m[15] = 1;
	}

	public void Copy( float[] matrix ) {
		m[ 0] = matrix[ 0]; m[ 4] = matrix[ 4]; m[ 8] = matrix[ 8]; m[12] = matrix[12];
		m[ 1] = matrix[ 1]; m[ 5] = matrix[ 5]; m[ 9] = matrix[ 9]; m[13] = matrix[13];
		m[ 2] = matrix[ 2]; m[ 6] = matrix[ 6]; m[10] = matrix[10]; m[14] = matrix[14];
		m[ 3] = matrix[ 3]; m[ 7] = matrix[ 7]; m[11] = matrix[11]; m[15] = matrix[15];
	}

	public void Copy( Matrix4x4f M ) {
		m[ 0] = M.m[ 0]; m[ 4] = M.m[ 4]; m[ 8] = M.m[ 8]; m[12] = M.m[12];
		m[ 1] = M.m[ 1]; m[ 5] = M.m[ 5]; m[ 9] = M.m[ 9]; m[13] = M.m[13];
		m[ 2] = M.m[ 2]; m[ 6] = M.m[ 6]; m[10] = M.m[10]; m[14] = M.m[14];
		m[ 3] = M.m[ 3]; m[ 7] = M.m[ 7]; m[11] = M.m[11]; m[15] = M.m[15];
	}

	public void Translate( Vector3f v ) {
		m[ 0] = 1; m[ 4] = 0; m[ 8] = 0; m[12] = v.Data[0];
		m[ 1] = 0; m[ 5] = 1; m[ 9] = 0; m[13] = v.Data[1];
		m[ 2] = 0; m[ 6] = 0; m[10] = 1; m[14] = v.Data[2];
		m[ 3] = 0; m[ 7] = 0; m[11] = 0; m[15] = 1;
	}

	public void Rotate( float angle_in_radians, Vector3f v ) {
		// TODO XXX assert here that v is normalized
		float c = (float)Math.cos( angle_in_radians );
		float s = (float)Math.sin( angle_in_radians );
		float one_minus_c = 1-c;
		m[ 0] = c + one_minus_c * v.Data[0]*v.Data[0];
		m[ 5] = c + one_minus_c * v.Data[1]*v.Data[1];
		m[10] = c + one_minus_c * v.Data[2]*v.Data[2];
		m[ 1] = m[ 4] = one_minus_c * v.Data[0]*v.Data[1];
		m[ 2] = m[ 8] = one_minus_c * v.Data[0]*v.Data[2];
		m[ 6] = m[ 9] = one_minus_c * v.Data[1]*v.Data[2];
		float xs = v.Data[0] * s;
		float ys = v.Data[1] * s;
		float zs = v.Data[2] * s;
		m[ 1] += zs;  m[ 4] -= zs;
		m[ 2] -= ys;  m[ 8] += ys;
		m[ 6] += xs;  m[ 9] -= xs;

		m[12] = 0;
		m[13] = 0;
		m[14] = 0;
		m[ 3] = 0; m[ 7] = 0; m[11] = 0; m[15] = 1;
	}

	public void Rotate( float angle_in_radians, Vector3f v, Vertex origin ) {
		Matrix4x4f tmp = new Matrix4x4f();
		tmp.Translate(new Vector3f(- origin.Position.Data[0], - origin.Position.Data[1], - origin.Position.Data[2]));
		Rotate( angle_in_radians, v);
		Copy(Matrix4x4f.Mult( this, tmp ));
		tmp.Translate(new Vector3f(origin.Position));
		Copy(Matrix4x4f.Mult( tmp, this ));
	}

	public void setToUniformScale( float s ) {
		m[ 0] = s; m[ 4] = 0; m[ 8] = 0; m[12] = 0;
		m[ 1] = 0; m[ 5] = s; m[ 9] = 0; m[13] = 0;
		m[ 2] = 0; m[ 6] = 0; m[10] = s; m[14] = 0;
		m[ 3] = 0; m[ 7] = 0; m[11] = 0; m[15] = 1;
	}

	public void setToUniformScale( float s, Vertex origin ) {
		Matrix4x4f tmp = new Matrix4x4f();
		tmp.Translate(new Vector3f(- origin.Position.Data[0], - origin.Position.Data[1], - origin.Position.Data[2]));
		setToUniformScale(s);
		Copy(Matrix4x4f.Mult(this, tmp));
		tmp.Translate(new Vector3f( origin.Position));
		Copy(Matrix4x4f.Mult(tmp, this));
	}

	public void LookAt(
		Vertex eye, Vertex target, Vector3f up,
		boolean inverted
	) {
		// step one: generate a rotation matrix

		Vector3f z = eye.Position.Difference(target.Position).Normalize();
		Vector3f y = up;
		Vector3f x = y.CrossProduct(z);
		y = z.CrossProduct(x);

		// Cross product gives area of parallelogram, which is < 1 for
		// non-perpendicular unit-length vectors; so normalize x and y.
		x = x.Normalize();
		y = y.Normalize();

		Matrix4x4f m2 = new Matrix4x4f();

		if ( inverted ) {
			// the rotation matrix
			m[ 0] = x.Data[0]; m[ 4] = y.Data[0]; m[ 8] = z.Data[0]; m[12] = 0;
			m[ 1] = x.Data[1]; m[ 5] = y.Data[1]; m[ 9] = z.Data[1]; m[13] = 0;
			m[ 2] = x.Data[2]; m[ 6] = y.Data[2]; m[10] = z.Data[2]; m[14] = 0;
			m[ 3] = 0;     m[ 7] = 0;     m[11] = 0;     m[15] = 1;

			// step two: premultiply by a translation matrix
			m2.Translate( eye.Position );
			Copy( Matrix4x4f.Mult(m2, this) );
		}
		else {
			// the rotation matrix
			m[ 0] = x.Data[0]; m[ 4] = x.Data[1]; m[ 8] = x.Data[2]; m[12] = 0;
			m[ 1] = y.Data[0]; m[ 5] = y.Data[1]; m[ 9] = y.Data[2]; m[13] = 0;
			m[ 2] = z.Data[0]; m[ 6] = z.Data[1]; m[10] = z.Data[2]; m[14] = 0;
			m[ 3] = 0;     m[ 7] = 0;     m[11] = 0;     m[15] = 1;

			// step two: postmultiply by a translation matrix
			m2.Translate( new Vector3f(- eye.Position.Data[0], - eye.Position.Data[1], - eye.Position.Data[2]) );
			Copy( Matrix4x4f.Mult(this, m2) );
		}
	}

	public void Perspective(float fovy, float aspect, float zNear, float zFar) {
		
		float f = (float) (1.0f / Math.tan(Math.toRadians(fovy) / 2));
		
		Matrix4x4f m2 = new Matrix4x4f();
		
		m2.LoadIdentity();
		m2.m[0] = f / aspect;
		m2.m[5] = f;
		m2.m[10] = (zFar + zNear) / (zNear - zFar);
		m2.m[11] = -1;
		m2.m[14] = (2 * zFar * zNear) / (zNear - zFar);
		m2.m[15] = 0;
		
		Copy(Matrix4x4f.Mult(m2, this));
		
	}
	
	// return the product of the given matrices
	public static Matrix4x4f Mult( Matrix4x4f a, Matrix4x4f b ) {
		Matrix4x4f M = new Matrix4x4f();

		M.m[ 0] = a.m[ 0]*b.m[ 0] + a.m[ 4]*b.m[ 1] + a.m[ 8]*b.m[ 2] + a.m[12]*b.m[ 3];
		M.m[ 1] = a.m[ 1]*b.m[ 0] + a.m[ 5]*b.m[ 1] + a.m[ 9]*b.m[ 2] + a.m[13]*b.m[ 3];
		M.m[ 2] = a.m[ 2]*b.m[ 0] + a.m[ 6]*b.m[ 1] + a.m[10]*b.m[ 2] + a.m[14]*b.m[ 3];
		M.m[ 3] = a.m[ 3]*b.m[ 0] + a.m[ 7]*b.m[ 1] + a.m[11]*b.m[ 2] + a.m[15]*b.m[ 3];

		M.m[ 4] = a.m[ 0]*b.m[ 4] + a.m[ 4]*b.m[ 5] + a.m[ 8]*b.m[ 6] + a.m[12]*b.m[ 7];
		M.m[ 5] = a.m[ 1]*b.m[ 4] + a.m[ 5]*b.m[ 5] + a.m[ 9]*b.m[ 6] + a.m[13]*b.m[ 7];
		M.m[ 6] = a.m[ 2]*b.m[ 4] + a.m[ 6]*b.m[ 5] + a.m[10]*b.m[ 6] + a.m[14]*b.m[ 7];
		M.m[ 7] = a.m[ 3]*b.m[ 4] + a.m[ 7]*b.m[ 5] + a.m[11]*b.m[ 6] + a.m[15]*b.m[ 7];

		M.m[ 8] = a.m[ 0]*b.m[ 8] + a.m[ 4]*b.m[ 9] + a.m[ 8]*b.m[10] + a.m[12]*b.m[11];
		M.m[ 9] = a.m[ 1]*b.m[ 8] + a.m[ 5]*b.m[ 9] + a.m[ 9]*b.m[10] + a.m[13]*b.m[11];
		M.m[10] = a.m[ 2]*b.m[ 8] + a.m[ 6]*b.m[ 9] + a.m[10]*b.m[10] + a.m[14]*b.m[11];
		M.m[11] = a.m[ 3]*b.m[ 8] + a.m[ 7]*b.m[ 9] + a.m[11]*b.m[10] + a.m[15]*b.m[11];

		M.m[12] = a.m[ 0]*b.m[12] + a.m[ 4]*b.m[13] + a.m[ 8]*b.m[14] + a.m[12]*b.m[15];
		M.m[13] = a.m[ 1]*b.m[12] + a.m[ 5]*b.m[13] + a.m[ 9]*b.m[14] + a.m[13]*b.m[15];
		M.m[14] = a.m[ 2]*b.m[12] + a.m[ 6]*b.m[13] + a.m[10]*b.m[14] + a.m[14]*b.m[15];
		M.m[15] = a.m[ 3]*b.m[12] + a.m[ 7]*b.m[13] + a.m[11]*b.m[14] + a.m[15]*b.m[15];

		return M;
	}

	// return the product of the given matrix and vector
	public static Vector3f Mult( Matrix4x4f a, Vector3f b ) {
		// We treat the vector as if
		// its (homogeneous) 4th component were zero.
		return new Vector3f(
			a.m[ 0]*b.Data[0] + a.m[ 4]*b.Data[1] + a.m[ 8]*b.Data[2], // + a.m[12]*b.w(),
			a.m[ 1]*b.Data[0] + a.m[ 5]*b.Data[1] + a.m[ 9]*b.Data[2], // + a.m[13]*b.w(),
			a.m[ 2]*b.Data[0] + a.m[ 6]*b.Data[1] + a.m[10]*b.Data[2]  // + a.m[14]*b.w(),
			// a.m[ 3]*b.x() + a.m[ 7]*b.y() + a.m[11]*b.z() + a.m[15]*b.w()
		);
	}

	// return the product of the given matrix and point
	public static Vertex Mult( Matrix4x4f a, Vertex b ) {
		// We treat the point as if
		// its (homogeneous) 4th component were one.
		return (new Vertex()).Position(
			a.m[ 0]*b.Position.Data[0] + a.m[ 4]*b.Position.Data[1] + a.m[ 8]*b.Position.Data[2] + a.m[12],
			a.m[ 1]*b.Position.Data[0] + a.m[ 5]*b.Position.Data[1] + a.m[ 9]*b.Position.Data[2] + a.m[13],
			a.m[ 2]*b.Position.Data[0] + a.m[ 6]*b.Position.Data[1] + a.m[10]*b.Position.Data[2] + a.m[14]
		);
	}

}
